﻿using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Diagnostics.Contracts;
using TaskManager.Domain.Repositories;
using TaskManager.Domain.Services;

namespace TaskManager.Domain.BusinessObjects
{
    /// <summary>
    /// Represents a task
    /// </summary>
    public class Task : IEntity<int>
    {
        private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(typeof(Task));

        /// <summary>
        /// Logger
        /// </summary>
        [NotMapped]
        public IUserActionLogger Logger { get; set; }

        /// <summary>
        /// Task unique identifier
        /// </summary>
        [Key]
        public int Id { get; set; }

        /// <summary>
        /// Reference to a project
        /// </summary>
        /// <remarks>Lazy-loaded property</remarks>
        public virtual Project Project { get; private set; }

        /// <summary>
        /// Logs
        /// </summary>
        /// <remarks>Lazy-loaded property</remarks>
        public virtual ICollection<TaskLogEntry> Logs { get; private set; }

        /// <summary>
        /// Comments
        /// </summary>
        /// <remarks>Lazy-loaded property</remarks>
        public virtual ICollection<Comment> Comments { get; private set; }

        /// <summary>
        /// Project identifier that is used as a foreign key
        /// </summary>
        public int ProjectId { get; set; }

        /// <summary>
        /// Task summary
        /// </summary>
        [Required]
        public string Summary { get; set; }

        /// <summary>
        /// Task description
        /// </summary>
        public string Description { get; set; }

        /// <summary>
        /// Task state
        /// </summary>
        [NotMapped]
        public TaskState State
        {
            get { return (TaskState)StateImpl; }
            private set { StateImpl = (int)value; }
        }

        /// <summary>
        /// Task state implementation
        /// </summary>
        /// <remarks>EF CF1 does not support mapping for enumerations,so we need to introduce 
        /// internal property for the sake of implementation</remarks>
        public int StateImpl { get; private set; }

        /// <summary>
        /// Date and time of creation
        /// </summary>
        [Required]
        public DateTime Created { get; private set; }

        /// <summary>
        /// Last modified time
        /// </summary>
        [Required]
        public DateTime Updated { get; private set; }

        /// <summary>
        /// Started time
        /// </summary>
        public DateTime? Started { get; private set; }

        /// <summary>
        /// Timestamp to prevent concurrent modifications
        /// </summary>
        [Timestamp]
        public byte[] RowVersion { get; set; }

        /// <summary>
        /// Task owner
        /// </summary>
        [Required]
        public string Owner { get; set; }

        /// <summary>
        /// Assignee
        /// </summary>
        [Required]
        public string Assignee { get; set; }

        /// <summary>
        /// Priority (0..4)
        /// </summary>
        [Required]
        public int Priority { get; set; }

        /// <summary>
        /// Parameterless constructor
        /// </summary>
        public Task()
        {
            if(Log.IsDebugEnabled) Log.Debug("Task parameterless constructor");
        }

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="project">Project</param>
        /// <param name="summary">Task summary</param>
        /// <param name="owner">Task owner</param>
        /// <param name="assignee">Assignee</param>
        /// <param name="priority">Task priority</param>
        /// <param name="logger">User operations logger</param>
        public Task(Project project, string summary, User owner, User assignee, int priority, IUserActionLogger logger)
            :this(project, summary, string.Empty, owner, assignee, priority, logger)
        {
            if (Log.IsInfoEnabled) Log.Info("Created task.Summary=[" + summary + "],project=[" + project.Name + "]");
        }

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="project">Project</param>
        /// <param name="summary">Task summary</param>
        /// <param name="description">Description</param>
        /// <param name="owner">Task owner</param>
        /// <param name="assignee">Assignee</param>
        /// <param name="priority">Task priority</param>
        /// <param name="logger">User operations logger</param>
        public Task(Project project, string summary, string description, User owner, User assignee, int priority, IUserActionLogger logger)
        {
            Contract.Requires(project != null);
            Contract.Requires(owner != null);
            Contract.Requires(assignee != null);
            Contract.Requires(logger != null);
            Contract.Requires(!string.IsNullOrEmpty(summary));

            Comments = new List<Comment>();
            Logs = new List<TaskLogEntry>();

            Project = project;
            Summary = summary;
            Description = description;
            Owner = owner.Name;
            Assignee = assignee.Name;
            Priority = priority;
            Updated = Created = DateTime.Now;
            State = TaskState.NotStarted;
            Logger = logger;

            project.AddTask(this);

            Logger.LogCreateTask(this);

            if (Log.IsInfoEnabled) Log.Info("Created task.Summary=[" + summary + "],project=[" + project.Name + "]");
        }

        /// <summary>
        /// Starts task
        /// </summary>
        public void Start()
        {
            Contract.Requires(State == TaskState.NotStarted);
            Contract.Ensures(State == TaskState.InProgress);

            State = TaskState.InProgress;
            Started = Updated = DateTime.Now;

            Logger.LogStartTask(this);

            if (Log.IsDebugEnabled) Log.Debug("Started task.Summary=[" + Summary + "]");
        }

        /// <summary>
        /// Completes task
        /// </summary>
        public void Complete()
        {
            Contract.Requires(State == TaskState.InProgress);
            Contract.Ensures(State == TaskState.Finished);

            State = TaskState.Finished;
            Started = Updated = DateTime.Now;

            Logger.LogCompleteTask(this);
            
            if (Log.IsDebugEnabled) Log.Debug("Completed task.Summary=[" + Summary + "]");
        }

        /// <summary>
        /// Reopens task
        /// </summary>
        public void Reopen()
        {
            Contract.Requires(State == TaskState.Finished);
            Contract.Ensures(State == TaskState.InProgress);

            State = TaskState.InProgress;
            Started = Updated = DateTime.Now;

            Logger.LogReopenTask(this);

            if(Log.IsDebugEnabled) Log.Debug("Reopened task.Summary=[" + Summary +"]");
        }

        /// <summary>
        /// Reassigns task to the new user
        /// </summary>
        /// <param name="newAssignee">New assignee</param>
        public void ReassignTo(string newAssignee)
        {
            Contract.Requires(!string.IsNullOrEmpty(newAssignee));
            Contract.Requires(Assignee != newAssignee);

            string oldAssignee = Assignee;
            Assignee = newAssignee;

            Logger.LogReassignTask(this, oldAssignee, Assignee);
            
            if (Log.IsDebugEnabled) Log.Debug("Reassigned task.Summary=[" + Summary + "],old assignee=[" + oldAssignee + "],new assignee=[" + Assignee +"]");
        }

        /// <summary>
        /// Sets new owner for task
        /// </summary>
        /// <param name="newOwner"></param>
        public void ChangeOwner(string newOwner)
        {
            Contract.Requires(!string.IsNullOrEmpty(newOwner));
            Contract.Requires(Owner != newOwner);

            string oldOwner = Owner;
            Owner = newOwner;

            Logger.LogChangeTaskOwner(this, oldOwner, newOwner);

            if(Log.IsDebugEnabled) Log.Debug("Changed owner.Summary=[" + Summary + "],old owner=[" + oldOwner + "],new owner=[" + Owner +"]");
        }

        /// <summary>
        /// Adds new comment
        /// </summary>
        /// <param name="comment">Comment</param>
        internal void AddComment(Comment comment)
        {
            Contract.Requires(State == TaskState.InProgress);

            Comments.Add(comment);

            Logger.LogAddComment(this, comment);

            if (Log.IsDebugEnabled) Log.Debug("Added comment to task.Summary=[" + Summary + "],comment=[" + comment + "]");
        }
    }
}